package com.acme.chat.server;

import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.acme.chat.client.ChatClient;
import com.acme.chat.message.MessageType;
import com.acme.chat.newclient.ClientMessage;
import com.acme.chat.newclient.ServerMessage;

// TODO: Change all 'users' on 'clients' or 'clients' on 'users', for consistency

/**
 * Implementation of Server interface. This implementation knows nothing about tcp or rmi.
 */
public class ServerImpl implements Server {
	private static final int AVERAGE_NAME_LENGTH = 25;
	private static final String ERROR_USER_EXISTS = "Another user with this name already on the server!\n";
	private static final String ERROR_TOO_LONG_NAME = "Your name is too long!\nMaximum allows is " + ChatClient.MAX_NAME_LENGTH + " symbols.\n";
	private static final String MESSAGE_DISCONNECTED = "You are disconnected from the server!\n";
	private static final String MESSAGE_LIST_OF_THE_USERS_IN_THE_ROOM = "List of the users in the room ";
	private static final String MESSAGE_LIST_OF_THE_USERS_ON_THE_SERVER = "List of the users on the server:\n";

	private Set<ChatClient> clients = new HashSet<ChatClient>();
	private Map<String, Set<ChatClient>> clientsInTheRoom = new HashMap<String, Set<ChatClient>>();

	private SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

	public ServerImpl() {

	}

	@Override
	public synchronized boolean addClient(ChatClient client) {
		if (userExists(client)) {
			ServerMessage message = new ServerMessage(MessageType.ERROR, ERROR_USER_EXISTS + getAllUserList());
			client.sendMessage(message);
			disconnectClient(client);
			return false;
		}

		if (nameIsTooLong(client)) {
		    ServerMessage message = new ServerMessage(MessageType.ERROR, ERROR_TOO_LONG_NAME);
			client.sendMessage(message);
			disconnectClient(client);
			return false;
		}

		if (clients.add(client) && addClientToRoom(client)) {
		    ServerMessage message = new ServerMessage(MessageType.CONNECTED);
		    client.sendMessage(message);
		    message = new ServerMessage(MessageType.MESSAGE, getUserConnectedString(client));
			sendMessageInRoom(client.getRoom(), message);
			System.out.println("Client added " + client.getName() + " clients size = " + clients.size());
			return true;
		}

		return false;
	}

	private boolean addClientToRoom(ChatClient client) {
		// add to the map
		Set<ChatClient> clients = getClientsFromRoom(client.getRoom());
		return clients.add(client);
	}

	private Set<ChatClient> getClientsFromRoom(String room) {
		Set<ChatClient> clients = clientsInTheRoom.get(room);
		if (clients == null) {
			clients = new HashSet<ChatClient>();
			clientsInTheRoom.put(room, clients);
		}
		return clients;
	}

	/**
	 * Return true if the another user with this name already connected to this
	 * server.
	 *
	 * @param client
	 * @return
	 */
	private boolean userExists(ChatClient client) {
		return clients.contains(client);
	}

	private boolean nameIsTooLong(ChatClient client) {
		return client.getName().length() > ChatClient.MAX_NAME_LENGTH;
	}

	/**
	 * Method removes client from the server
	 *
	 * @param client
	 */
	@Override
	public synchronized boolean removeClient(ChatClient client) {
		removeClientFromRoom(client);
		return clients.remove(client);
	}

	private void removeClientFromRoom(ChatClient client) {
		// remove from the map
		String room = client.getRoom();
		Set<ChatClient> chatClients = clientsInTheRoom.get(room);
		chatClients.remove(client);
	}

	@Override
	public synchronized void processMessage(ClientMessage message, ChatClient client) {
		switch (message.getType()) {
		case MESSAGE:
			sendCommonMessageFromClient(message, client);
			break;
		case LOGOUT:
		case DISCONNECT:
			disconnectClient(client);
			break;
		case WHOISIN:
			sendListOfUsers(client);
			break;
		case PRIVATE:
			sendPrivateMessageFromClient(message, client);
			break;
		case CHANGE_ROOM:
		    changeRoom(message, client);
		    break;
		default:
			break;
		}
	}

	private synchronized void changeRoom(ClientMessage message, ChatClient client) {
	    String oldRoom = client.getRoom();
	    String newRoom = message.getRoom();
	    sendMessageInRoom(oldRoom, new ServerMessage(MessageType.MESSAGE, getUserDisconnectedString(client)));
	    removeClientFromRoom(client);
	    client.changeRoom(newRoom);
	    addClientToRoom(client);
	    sendMessageInRoom(newRoom, new ServerMessage(MessageType.MESSAGE, getUserConnectedString(client)));
	}

    private void sendPrivateMessageFromClient(ClientMessage message, ChatClient client) {
        String composeString = getDateFormatted() + ": private " + client.getName() + ": " + message.getMessage() + "\n";
        ServerMessage composeMessage = new ServerMessage(MessageType.MESSAGE, composeString);
        client.sendMessage(composeMessage);
        for (ChatClient cc : clients) {
            if (cc.getName().equals(message.getNameToWho())) {
                cc.sendMessage(composeMessage);
                break;
            }
        }
    }

	private void sendCommonMessageFromClient(ClientMessage message, ChatClient client) {
		String composeString = getDateFormatted() + ": " + client.getName() + ": " + message.getMessage() + "\n";
        ServerMessage composeMessage = new ServerMessage(MessageType.MESSAGE, composeString);
		sendMessageInRoom(client.getRoom(), composeMessage);
	}

	private void sendMessageInRoom(String room, ServerMessage message) {
		Set<ChatClient> clients = clientsInTheRoom.get(room);
		for (ChatClient client : clients) {
			client.sendMessage(message);
		}
	}

	private void disconnectClient(ChatClient client) {
        removeClient(client);
        client.disconnect();
        sendMessageInRoom(client.getRoom(), new ServerMessage(MessageType.MESSAGE, getUserDisconnectedString(client)));
        System.out.println("Client removed " + client.getName() + " clients size = " + clients.size());
    }

	private String getUserConnectedString(ChatClient client) {
		return "User: < " + client.getName() + " > connected to the room < " + client.getRoom() + " >\n";
	}

	private String getUserDisconnectedString(ChatClient client) {
		return "User: < " + client.getName() + " > disconnected from the room < " + client.getRoom() + " >\n";
	}

	private void sendListOfUsers(ChatClient client) {
		String userList = getUserListFromTheRoom(client.getRoom());
		ServerMessage message = new ServerMessage(MessageType.MESSAGE, userList);
		client.sendMessage(message);
	}

	private String getUserListFromTheRoom(String room) {
		Set<ChatClient> clients = getClientsFromRoom(room);
		StringBuilder sb = new StringBuilder(clients.size() * AVERAGE_NAME_LENGTH);
		sb.append(MESSAGE_LIST_OF_THE_USERS_IN_THE_ROOM).append(room).append(":\n");
		for (ChatClient client : clients) {
			sb.append("< ").append(client.getName()).append(" >\n");
		}
		return sb.toString();
	}

	private String getAllUserList() {
		StringBuilder sb = new StringBuilder(clients.size() * AVERAGE_NAME_LENGTH);
		sb.append(MESSAGE_LIST_OF_THE_USERS_ON_THE_SERVER);
		for (ChatClient client : clients) {
			sb.append("< ").append(client.getName()).append(" >\n");
		}
		return sb.toString();
	}

	private String getDateFormatted() {
		return dateFormat.format(new Date());
	}

	@Override
	public Set<ChatClient> getClients() {
		return Collections.unmodifiableSet(clients);
	}

	@Override
	public Set<String> getRooms() {
		return Collections.unmodifiableSet(clientsInTheRoom.keySet());
	}

	@Override
	public Map<String, Set<ChatClient>> getClientsInTheRoom() {
		return Collections.unmodifiableMap(clientsInTheRoom);
	}

}
